package cmich.cps.scheduler.algorithms;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;

import cmich.cps.scheduler.data.AssignedCourse;
import cmich.cps.scheduler.data.Course;
import cmich.cps.scheduler.data.Preference;
import cmich.cps.scheduler.data.Professor;
import cmich.cps.scheduler.data.Room;
import cmich.cps.scheduler.data.Schedule;
import cmich.cps.scheduler.gui.Prompts;

/**
 * The Class Validator.
 *
 * @author Jonathan Kissinger <kissi1j@cmich.edu>
 */
public class Validator {
	
	/** The Constant LOGGER. */
	private final static Logger LOGGER = Logger.getLogger(Validator.class.getName());
	
	/** The system. */
	private AlgorithmSystem system;
	
	/** The prompts. */
	private Prompts prompts = Prompts.getInstance();

	/**
	 * Instantiates a new validator.
	 *
	 * @param system the system
	 */
	Validator(AlgorithmSystem system) {
		this.system = system;
	}

	/**
	 * Pre-validate variables.
	 *
	 * @return true, if all checks pass
	 */
	boolean preValidate() {
		boolean result = checkAtLeastOne();
		result = result && checkProfessorsPreferences();
		result = result && checkCoursesPreferences();
		result = result && checkManuallyAssignedCourses();
		result = result && checkTotalMaxCourses();
		result = result && checkRoomTimeslots();
		return result;
	}

	/**
	 * Check that there are enough timeslots and rooms for all courses to be assigned.
	 * 
	 * @return true, if checks out
	 */
	private boolean checkRoomTimeslots() {
		int totalRoomTimeslots = 0;
		for(Room room : system.getRooms()) {
			if(!room.getLab()) {
				totalRoomTimeslots = totalRoomTimeslots + system.getTimeslots().size();
			}
		}
		int numCourses = 0;
		for(Course course : system.getCourses()) {
			if(course.getRoomPreference().equals("Any")) {
				numCourses++;
			}
		}
		if(totalRoomTimeslots<numCourses) {
			String msg = "Cannot assign all courses, limited by the number of available rooms & timeslots.";
			LOGGER.info(msg);
			prompts.nonFatalError(msg);
			return false;
		}
		return true;
	}

	/**
	 * Check that the number of courses is not less than the
	 * total of all professor maximum courses.
	 * 
	 * @return true, if checks out
	 */
	private boolean checkTotalMaxCourses() {
		int numCourses = system.getCourses().size();
		Integer totalMaxCourses = 0;
		for(Professor professor : system.getProfessors()) {
			totalMaxCourses = totalMaxCourses + professor.getMaxCourses();
		}
		if(totalMaxCourses<numCourses) {
			String msg = "Cannot assign all courses, limited by professor max courses.";
			LOGGER.info(msg);
			prompts.nonFatalError(msg);
			return false;
		}
		return true;
	}

	/**
	 * Check manually assigned courses.
	 *
	 * @return true, if successful
	 */
	private boolean checkManuallyAssignedCourses() {
		Schedule schedule = system.getMasterSchedule();
		for(AssignedCourse course : schedule.getCourses()) {
			String courseName = course.getName() + " (Section: " + course.getSectionNumber() + ")";
			String msg = "Prevalidation failed: Manually assigned course " + courseName + " ";
			if(!checkAssignedCourse(course)) {
				msg = msg + " no longer exists. Please correct and try again.";
				LOGGER.config(msg);
				prompts.nonFatalError(msg);
				return false;
			}
			if(!checkAssignedProfessors(course)) {
				msg = msg + " was assigned to a professor, " + course.getProfessor() + ", that no longer exists. Please correct and try again.";
				LOGGER.config(msg);
				prompts.nonFatalError(msg);
				return false;
			}
			if(!checkRooms(course.getRoom())) {
				msg = msg + " was assigned to a room, " + course.getRoom() + ", that no longer exists. Please correct and try again.";
				LOGGER.config(msg);
				prompts.nonFatalError(msg);
				return false;
			}
		}
		return true;
	}

	/**
	 * Check assigned professors.
	 *
	 * @param course the course
	 * @return true, if successful
	 */
	private boolean checkAssignedProfessors(AssignedCourse course) {
		for(int i=0;i<system.getProfessors().size();i++) {
			Professor p = system.getProfessors().get(i);
			if(p.getName().equals(course.getProfessor())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check assigned course.
	 *
	 * @param course the course
	 * @return true, if successful
	 */
	private boolean checkAssignedCourse(AssignedCourse course) {
		for(int i=0;i<system.getCourses().size();i++) {
			Course c = system.getCourses().get(i);
			if(c.getName().equals(course.getName()) && c.getSectionNumber().equals(course.getSectionNumber())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check at least one.
	 *
	 * @return true, if successful
	 */
	private boolean checkAtLeastOne() {
		String msg = "";
		if(system.getCourses().size()==0) {
			msg = "There must be at least one course.";
		}
		if(system.getRooms().size()==0) {
			msg = "There must be at least one room.";
		}
		if(system.getProfessors().size()==0) {
			msg = "There must be at least one professor.";
		}
		if(msg.isEmpty()) {
			return true;
		}
		LOGGER.info(msg);
		prompts.nonFatalError(msg);
		return false;
	}

	/**
	 * Check professors preferences.
	 *
	 * @return true, if successful
	 */
	private boolean checkProfessorsPreferences() {
		LOGGER.fine("Beginning preferences check.");
		for(int i=0;i<system.getProfessors().size();i++) {
			Professor professor = system.getProfessors().get(i);
			for(int j=0;j<professor.getPreferences().size();j++) {
				Preference preference = professor.getPreferences().get(j);
				boolean result = false;
				result = checkCourses(preference.getItem());
				if(!result) {
					result = checkRooms(preference.getItem());
				}
				if(!result) {
					result = system.getTimeslots().contains(preference.getItem());
				}
				if(!result) {
					String msg = professor + " has an invalid preference for " + preference.getItem() + " because it no longer exists.  Cancelling scheduler, please correct and try again.";
					LOGGER.info(msg);
					prompts.nonFatalError(msg);
					return false;
				}
			}
		}
		LOGGER.config("Preferences all checked out ok.");
		return true;
	}

	/**
	 * Check rooms.
	 *
	 * @param roomName the room name
	 * @return true, if successful
	 */
	private boolean checkRooms(String roomName) {
		for(int i=0;i<system.getRooms().size();i++) {
			Room room = system.getRooms().get(i);
			if(room.getName().equals(roomName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check courses.
	 *
	 * @param courseName the course name
	 * @return true, if successful
	 */
	boolean checkCourses(String courseName) {
		for(int i=0;i<system.getCourses().size();i++) {
			Course course = system.getCourses().get(i);
			if(course.getName().equals(courseName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Check courses preferences.
	 *
	 * @return true, if successful
	 */
	private boolean checkCoursesPreferences() {
		for(int i=0;i<system.getCourses().size();i++) {
			Course course = system.getCourses().get(i);
			String roomName = course.getRoomPreference();
			if(!roomName.equals("Any") && !checkRooms(roomName)) {
				String msg = course.getName() + "(Section: " + course.getSectionNumber() + ") is supposed to be in a room that no longer exists, " + roomName + ".  Cancelling scheduler, please correct and try again.";
				LOGGER.info(msg);
				prompts.nonFatalError(msg);
				return false;
			}
		}
		return true;
	}

	/**
	 * Post validate.
	 *
	 * @param schedule the schedule
	 * @return true, if successful
	 */
	boolean postValidate(Schedule schedule) {
		for(Professor professor : system.getProfessors()) {
			if(professor.getAssignedCourses().equals(0)) {
				LOGGER.config("Post Validation failed: " + professor.getName() + " has no assigned courses, post validation failed.");
				return false;
			}
		}
		system.resetVariables();
		if(schedule.getCourses().size()!=system.getCourses().size()) {
			LOGGER.config("Post Validation failed: Not every course was assigned (" + schedule.getCourses().size() + "/" + system.getCourses().size() + ").");
			return false;
		}
		return true;
	}
	
	/**
	 * Gets the timestamp.
	 *
	 * @return the timestamp
	 */
	String getTimestamp() {
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd h:mm a");
		String timestamp = sdf.format(date);
		return timestamp;
	}
}